package ui;

import java.awt.Graphics;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.FontMetrics;

/**
*	A class for simple x-y plots, showing up to 3 traces in a cartesian
* coordinate system.
*
* @version 1.0
* @author Hans U. Gerber (<a href="mailto:gerber@ifh.ee.ethz.ch">gerber@ifh.ee.ethz.ch</a>)
*/
public class XYPlot extends Plot {
	RealPoint[][] trace;
	Dimension previousSize = null;

	public XYPlot() {
		trace = new RealPoint[3][];
		setWindow(0.0, -1.1, 1.05, 1.1);
	}

	void setViewport() {
		Dimension d = size();
		super.setViewport(4 + textWidth + 3 + 3, 4, d.width - 4, d.height - 4);
	}

	//	private int counter = 0;

	/**
	* Updates one of 3 traces; the trace is immediately redrawn.
	*/
	public synchronized void setTrace(int which, RealPoint[] p) {
		//	I want to update a new trace as quickly and as smoothly as I can.
		//	I don't use double-buffering because it does not make sense to
		//	move whole bitmaps worth kilobytes when just a few pixels have to
		//	be updated.
		//	Requesting repaint() does not work with me because
		//	- it "calls update() as soon as possible" and several repaint
		//	  requests may be merged into one
		//	-	by calling update(), the background would be erased
		//	getGraphics() should work fine for this task but then I ran into
		//	a JDK AWT bug. It showed up with appletviewer and HotJava:
		//	I should be able to getGraphics() as many times as I want.
		//	But it seems to me that Windows resources (DCs?) are not properly
		//	freed. After a lot of calls, getGraphics() returns null.
		//	MS' jview and Explorer 3.0 ran fine, however.
		//	When I replaced MS' implementation of Win32Graphics.class in Sun's
		//	classes.zip, the problem went away. As a precaution, I now explicitely
		//	dispose() the graphics context.
		//	But then, this happened: 
		//	When testing the applet with Sun's JDK java interpreter and 
		//	appletviewer under Windows 95, the plot did not appear initially.
		//	I noticed that setTrace() caused paint() to be called when the
		//	size of the canvas was still [0,0]. Maybe the AWT then assumed that
		//	the canvas was already painted and did not repaint again.
		//	Now I have setTrace() call paint() only if the size is not zero:
		//	
		Graphics g = getGraphics();
		Dimension d = size();
		boolean canPaint = (g != null && d.width != 0 && d.height != 0);
		if (canPaint) {
			//	Erase the current image of the trace by painting it with
			//	the background color. This may cut holes into other traces
			//	and the axis. Therefore, I will repaint the whole plot after
			//	this:
			setViewport();
			drawTrace(g, trace[which], getBackground());
		}
		trace[which] = p;
		if (canPaint) {
			//	Repaint all to fix any holes:
			paint(g);
		}
		if (g != null) {
			//	As a quick fix for Sun's Windows resource leak bug,
			//	I explicitely free the graphics context:
			g.dispose();
			//	(Before  knew about dispose(), I used to do this:
			//	Collect garbage from time to time, hoping that
			//	Windows resources are freed as well:
			//
			//	counter++;
			//	if (counter > 100) {
			//		System.gc();
			//		counter = 0;
			//	}
		}
	}

	static final int FRAME_WIDTH = 4;
	int textWidth = 0;
	int textAscent = 0;

	/**
	* This method repaints the plot completely.
	*/
	public synchronized void paint(Graphics g) {
		//	After resizing a component, the AWT doesn't seem to call neither
		//	repaint() nor update(). It goes directly at paint().
		//	But I want a cleanly erased canvas after a resize, because not all
		//	of my drawing takes place in repaint().
		//	I force an update() (and therefore erasing the canvas)
		//	whenever paint() notices that the size of the canvas has changed:
		//
		Dimension d = size();
		if (previousSize == null || previousSize.width != d.width || previousSize.height != d.height) {
			previousSize = d;
			update(g);
			//	This will erase the background and then call paint() recursively.
			//	Recursion stops as soon as changing the size stops.
		}

		if (textWidth == 0) {
			FontMetrics metrics = g.getFontMetrics();
			textWidth = metrics.stringWidth("+1");
			textAscent = metrics.getAscent();
		}

		//	Draw a 3-D frame:
		Color bg = getBackground();
		g.setColor(bg);
		g.draw3DRect(0, 0, d.width - 1, d.height - 1, true);
		g.draw3DRect(3, 3, d.width - 7,	d.height - 7, false);
		g.setColor(Color.black);
		g.clipRect(4, 4, d.width - 8, d.height - 8);		
		setViewport();
		g.setColor(Color.gray);
		g.drawLine(xformX(xMin), xformY(0), xformX(xMax), xformY(0));
		g.drawLine(xformX(0.0), xformY(yMax), xformX(0.0), xformY(yMin));
		g.drawString("0", xformX(0.0)-textWidth, xformY(0.0)-1); 
		g.drawLine(xformX(1.0), xformY(0)-3, xformX(1.0), xformY(0.0)+3);
		g.drawString(" 1", xformX(1.0)-textWidth/2, xformY(0.0)+3+textAscent); 
		g.drawLine(xformX(0.0)-3, xformY(1.0), xformX(0.0)+3, xformY(1.0));
		g.drawString("+1", xformX(0.0)-3-textWidth-1, xformY(1.0)+textAscent/2); 
		g.drawLine(xformX(0.0)-3, xformY(-1.0), xformX(0.0)+3, xformY(-1.0));
		g.drawString("-1", xformX(0.0)-3-textWidth-1, xformY(-1.0)+textAscent/2); 
		drawTrace(g, trace[0], new Color(255, 0, 0));
		drawTrace(g, trace[1], new Color(0, 0, 0));
		drawTrace(g, trace[2], new Color(0, 0, 255));
	}

	/**
	* Draws one of the 3 traces.
	*/
	synchronized void drawTrace(Graphics g, RealPoint[] trace, Color color) {
		if (trace != null) {
			int nrOfPoints = trace.length;
			int[] x = new int[nrOfPoints];
			int[] y = new int[nrOfPoints];
			for (int i = 0; i < nrOfPoints; i++) {
				x[i] = xformX(trace[i].x);
				y[i] = xformY(trace[i].y);
			}
			g.setColor(color);
			//  With older Java VMs, drawPolygon did not close the polygon.
			//  The newest VMs, however, do close it; and the documentation says so.
			//  This is dumb. A programmer can easily close a polyline, if he wants to.
			//  But there's no easy way to keep drawPolygon from closing the line.
			//  Do I really have to resort to a series of drawLines?
			//
			//  g.drawPolygon(x, y, nrOfPoints);
			for (int i = 0; i < nrOfPoints-1; i++) {
				g.drawLine(x[i], y[i], x[i+1], y[i+1]);
			}
		}
	}
}
